package com.travelgroup.service.impl;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.travelgroup.constant.RedisConstant;
import com.travelgroup.constant.ScenicSpotConstant;
import com.travelgroup.domain.dto.LocationDTO;
import com.travelgroup.domain.entity.District;
import com.travelgroup.domain.entity.Location;
import com.travelgroup.enums.LocationType;
import com.travelgroup.exception.BusinessException;
import com.travelgroup.exception.R;
import com.travelgroup.mapper.LocationMapper;
import com.travelgroup.service.IDistrictService;
import com.travelgroup.service.ILocationService;
import com.travelgroup.utils.MapUtil;
import com.travelgroup.utils.RedisUtil;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Service
@Slf4j
public class LocationServiceImpl extends ServiceImpl<LocationMapper, Location>
        implements ILocationService {
    @Resource
    private IDistrictService distinctService;
    @Resource
    private MapUtil mapUtil;
    @Resource
    private RedisUtil redisUtil;

    /**
     * 行政区划查询
     * @param current
     * @param size
     * @param searchDTO
     * @return
     */
    @Override
    public R<List<District>> getAdministrative(Integer current, Integer size, LocationDTO searchDTO) {
        String code = searchDTO.getCityCode();

        // 获取下一级行政区
        List<District> list = distinctService.lambdaQuery()
                .eq(District::getParentCode, code)
                .like(District::getName, "%" + searchDTO.getKeyword() + "%")
                .list();
        if (list != null && !list.isEmpty() && list.size() >= size) {return R.success("获取下一级行政区成功", list);}

        // 父级行政区不存在则抛出异常
        District district = distinctService.lambdaQuery().eq(District::getCode, code).one();
        if (district == null) throw new BusinessException("父级行政区不存在");

        // 检查是否存在下级行政区
        Integer leaf = district.getLeaf().getValue();
        if (leaf >= 4) throw new RuntimeException("无下级行政区");

        Map<String, Object> params = mapUtil.buildSearchParams(current, size, 12, searchDTO);
        MapUtil.ResponseParser<District> responseParser = mapUtil.createDistrictParser(code, leaf, distinctService);

        List<District> districtList = mapUtil.executeSearch(params, responseParser);
        distinctService.saveBatch(districtList);

        return R.success("获取行政区划成功", districtList);
    }

    /**
     * 天地图API获取地点信息
     * @param current
     * @param size
     * @param searchDTO
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public R<List<Location>> getAttractions(Integer current, Integer size, LocationDTO searchDTO) {
        // 构造查询参数
        if (searchDTO.getType() == null || StrUtil.isBlank(searchDTO.getType())) {
            if (LocationType.TRAFFIC == searchDTO.getLocationType()) {
                searchDTO.setType(ScenicSpotConstant.getAllTransportationTypes());
            } else if (LocationType.PLACE == searchDTO.getLocationType()) {
                searchDTO.setType(ScenicSpotConstant.getAllScenicTypes());
            } else if (LocationType.FOOD == searchDTO.getLocationType()) {
                searchDTO.setType(ScenicSpotConstant.getAllFoodTypes());
            } else if (LocationType.HOTEL == searchDTO.getLocationType()) {
                searchDTO.setType(ScenicSpotConstant.getAllHotelTypes());
            }
        }
        Map<String, Object> params;
        if (searchDTO.getKeyword() == null || StrUtil.isBlank(searchDTO.getKeyword())) {
            // 无关键词搜索
            params = mapUtil.buildSearchParams(current, size, 13, searchDTO);
        } else {
            // 有关键词搜索
            params = mapUtil.buildSearchParams(current, size, 12, searchDTO);
        }

        // 获取数据库数据
        List<Location> list = searchFromDataBase(current, size, searchDTO, params);
        if (list != null && !list.isEmpty() && list.size() >= size) return R.success("景点信息查找成功", list);

        List<String> typeList = Arrays.stream(
                String.valueOf(params.get("dataTypes")).split(","))
                .toList();

        // 进行批量查询
        List<Location> result = mapUtil.executeBatchSearch(params, typeList, 3,
                type -> mapUtil.createLocationParser(searchDTO.getCityCode(), type)
        );

        // 检查API调用是否成功
        if (result.isEmpty()) throw new BusinessException("地图API查询失败");
        Map<String, Location> uniqueLocations = new HashMap<>();
        for (Location location : result) {
            String key = location.getName() + "|" + location.getAddress() + "|" +
                    location.getLongitude() + "|" + location.getLatitude();
            uniqueLocations.put(key, location);
        }

        List<Location> distinctResult = new ArrayList<>(uniqueLocations.values());

        // 过滤已存在的数据
        List<Location> saveList = distinctResult.stream()
                .filter(location -> !this.lambdaQuery()
                        .eq(Location::getName, location.getName())
                        .eq(Location::getCityCode, location.getCityCode())
                        .eq(Location::getLongitude, location.getLongitude())
                        .eq(Location::getLatitude, location.getLatitude())
                        .exists())
                .collect(Collectors.toList());

        // 保存新数据
        if (!saveList.isEmpty() && !this.saveBatch(saveList)) {
            throw new BusinessException("地点数据存储失败");
        }

        return R.success("查询地点成功", result);
    }

    /**
     * 数据库查询数据
     * @param current
     * @param size
     * @param searchDTO
     * @param params
     * @return
     */
    private List<Location> searchFromDataBase(Integer current, Integer size, LocationDTO searchDTO, Map<String, Object> params) {
        LambdaQueryWrapper<Location> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        log.info("specify: ");
        if (params.get("specify") != null) {
            lambdaQueryWrapper.eq(Location::getCityCode, params.get("specify"));
        }
        log.info("keyword: ");
        if (params.get("keyWord") != null) {
            String keyword = (String) params.get("keyWord");
            lambdaQueryWrapper.and(wrapper -> wrapper
                    .like(Location::getName, keyword)
                    .or()
                    .like(Location::getAddress, keyword));
        }
        log.info("dataTypes: ");
        if (params.get("dataTypes") != null) {
            lambdaQueryWrapper.in(Location::getType, Arrays.stream(String.valueOf(params.get("dataTypes")).split(",")).toList());
        }
        log.info("latlot:");
        if (searchDTO.getLongitude() != null && searchDTO.getLatitude() != null &&
                searchDTO.getRadius() != null) {
            String sql = "st_distance_sphere(point(#{longitude},#{latitude}), point(#{longitude},#{latitude})) <= #{radius}";
            lambdaQueryWrapper.apply(sql,
                    "longitude", searchDTO.getLongitude(),
                    "latitude", searchDTO.getLatitude(),
                    "radius", searchDTO.getRadius());
        }
        Page<Location> page = baseMapper.selectPage(new Page<>(current, size), lambdaQueryWrapper);
        return page.getRecords();
    }

    public Location getByRedis(Long id) {
        return redisUtil.queryById(RedisConstant.LOCATION_KEY, id, Location.class,
                this::getById, RedisConstant.CACHE_TTL, TimeUnit.MINUTES);
    }

}
